// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_server_properties_manager.h"

#include <memory>
#include <utility>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace net {

namespace {

    using base::StringPrintf;
    using ::testing::_;
    using ::testing::Invoke;
    using ::testing::Mock;
    using ::testing::StrictMock;

    class MockPrefDelegate : public net::HttpServerPropertiesManager::PrefDelegate {
    public:
        MockPrefDelegate() { }
        ~MockPrefDelegate() override { }

        // HttpServerPropertiesManager::PrefDelegate implementation.
        bool HasServerProperties() override { return true; }
        const base::DictionaryValue& GetServerProperties() const override
        {
            return prefs_;
        }
        void SetServerProperties(const base::DictionaryValue& value) override
        {
            prefs_.Clear();
            prefs_.MergeDictionary(&value);
            if (!prefs_changed_callback_.is_null())
                prefs_changed_callback_.Run();
        }
        void StartListeningForUpdates(const base::Closure& callback) override
        {
            CHECK(prefs_changed_callback_.is_null());
            prefs_changed_callback_ = callback;
        }
        void StopListeningForUpdates() override
        {
            CHECK(!prefs_changed_callback_.is_null());
            prefs_changed_callback_ = base::Closure();
        }

        void SetPrefs(const base::DictionaryValue& value)
        {
            // prefs_ = value;
            prefs_.Clear();
            prefs_.MergeDictionary(&value);
            if (!prefs_changed_callback_.is_null())
                prefs_changed_callback_.Run();
        }

    private:
        base::DictionaryValue prefs_;
        base::Closure prefs_changed_callback_;

        DISALLOW_COPY_AND_ASSIGN(MockPrefDelegate);
    };

    class TestingHttpServerPropertiesManager : public HttpServerPropertiesManager {
    public:
        TestingHttpServerPropertiesManager(
            HttpServerPropertiesManager::PrefDelegate* pref_delegate,
            scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
            : HttpServerPropertiesManager(pref_delegate, io_task_runner)
        {
            InitializeOnNetworkThread();
        }

        ~TestingHttpServerPropertiesManager() override { }

        // Make these methods public for testing.
        using HttpServerPropertiesManager::ScheduleUpdateCacheOnPrefThread;

        // Post tasks without a delay during tests.
        void StartPrefsUpdateTimerOnNetworkThread(base::TimeDelta delay) override
        {
            HttpServerPropertiesManager::StartPrefsUpdateTimerOnNetworkThread(
                base::TimeDelta());
        }

        void UpdateCacheFromPrefsOnUIConcrete()
        {
            HttpServerPropertiesManager::UpdateCacheFromPrefsOnPrefThread();
        }

        // Post tasks without a delay during tests.
        void StartCacheUpdateTimerOnPrefThread(base::TimeDelta delay) override
        {
            HttpServerPropertiesManager::StartCacheUpdateTimerOnPrefThread(
                base::TimeDelta());
        }

        void UpdatePrefsFromCacheOnNetworkThreadConcrete(
            const base::Closure& callback)
        {
            HttpServerPropertiesManager::UpdatePrefsFromCacheOnNetworkThread(callback);
        }

        void ScheduleUpdatePrefsOnNetworkThreadConcrete(Location location)
        {
            HttpServerPropertiesManager::ScheduleUpdatePrefsOnNetworkThread(location);
        }

        void ScheduleUpdatePrefsOnNetworkThread()
        {
            // Picked a random Location as caller.
            HttpServerPropertiesManager::ScheduleUpdatePrefsOnNetworkThread(
                DETECTED_CORRUPTED_PREFS);
        }

        MOCK_METHOD0(UpdateCacheFromPrefsOnPrefThread, void());
        MOCK_METHOD1(UpdatePrefsFromCacheOnNetworkThread, void(const base::Closure&));
        MOCK_METHOD1(ScheduleUpdatePrefsOnNetworkThread, void(Location location));
        MOCK_METHOD7(UpdateCacheFromPrefsOnNetworkThread,
            void(std::vector<std::string>* spdy_servers,
                SpdySettingsMap* spdy_settings_map,
                AlternativeServiceMap* alternative_service_map,
                IPAddress* last_quic_address,
                ServerNetworkStatsMap* server_network_stats_map,
                QuicServerInfoMap* quic_server_info_map,
                bool detected_corrupted_prefs));
        MOCK_METHOD7(UpdatePrefsOnPrefThread,
            void(base::ListValue* spdy_server_list,
                SpdySettingsMap* spdy_settings_map,
                AlternativeServiceMap* alternative_service_map,
                IPAddress* last_quic_address,
                ServerNetworkStatsMap* server_network_stats_map,
                QuicServerInfoMap* quic_server_info_map,
                const base::Closure& completion));

    private:
        DISALLOW_COPY_AND_ASSIGN(TestingHttpServerPropertiesManager);
    };

} // namespace

// TODO(rtenneti): After we stop supporting version 3 and everyone has migrated
// to version 4, delete the following code.
static const int kHttpServerPropertiesVersions[] = { 3, 4, 5 };

class HttpServerPropertiesManagerTest : public testing::TestWithParam<int> {
protected:
    HttpServerPropertiesManagerTest() { }

    void SetUp() override
    {
        one_day_from_now_ = base::Time::Now() + base::TimeDelta::FromDays(1);
        pref_delegate_ = new MockPrefDelegate;
        http_server_props_manager_.reset(
            new StrictMock<TestingHttpServerPropertiesManager>(
                pref_delegate_, base::ThreadTaskRunnerHandle::Get()));
        ExpectCacheUpdate();
        base::RunLoop().RunUntilIdle();
    }

    void TearDown() override
    {
        if (http_server_props_manager_.get())
            http_server_props_manager_->ShutdownOnPrefThread();
        base::RunLoop().RunUntilIdle();
        http_server_props_manager_.reset();
    }

    void ExpectCacheUpdate()
    {
        EXPECT_CALL(*http_server_props_manager_, UpdateCacheFromPrefsOnPrefThread())
            .WillOnce(Invoke(http_server_props_manager_.get(),
                &TestingHttpServerPropertiesManager::
                    UpdateCacheFromPrefsOnUIConcrete));
    }

    void ExpectScheduleUpdatePrefsOnNetworkThread()
    {
        EXPECT_CALL(*http_server_props_manager_,
            ScheduleUpdatePrefsOnNetworkThread(_))
            .WillOnce(Invoke(http_server_props_manager_.get(),
                &TestingHttpServerPropertiesManager::
                    ScheduleUpdatePrefsOnNetworkThreadConcrete));
    }

    void ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly()
    {
        EXPECT_CALL(*http_server_props_manager_,
            ScheduleUpdatePrefsOnNetworkThread(_))
            .WillRepeatedly(Invoke(http_server_props_manager_.get(),
                &TestingHttpServerPropertiesManager::
                    ScheduleUpdatePrefsOnNetworkThreadConcrete));
    }

    void ExpectPrefsUpdate()
    {
        EXPECT_CALL(*http_server_props_manager_,
            UpdatePrefsFromCacheOnNetworkThread(_))
            .WillOnce(Invoke(http_server_props_manager_.get(),
                &TestingHttpServerPropertiesManager::
                    UpdatePrefsFromCacheOnNetworkThreadConcrete));
    }

    void ExpectPrefsUpdateRepeatedly()
    {
        EXPECT_CALL(*http_server_props_manager_,
            UpdatePrefsFromCacheOnNetworkThread(_))
            .WillRepeatedly(
                Invoke(http_server_props_manager_.get(),
                    &TestingHttpServerPropertiesManager::
                        UpdatePrefsFromCacheOnNetworkThreadConcrete));
    }

    bool HasAlternativeService(const url::SchemeHostPort& server)
    {
        const AlternativeServiceVector alternative_service_vector = http_server_props_manager_->GetAlternativeServices(server);
        return !alternative_service_vector.empty();
    }

    MockPrefDelegate* pref_delegate_; // Owned by HttpServerPropertiesManager.
    std::unique_ptr<TestingHttpServerPropertiesManager>
        http_server_props_manager_;
    base::Time one_day_from_now_;

private:
    DISALLOW_COPY_AND_ASSIGN(HttpServerPropertiesManagerTest);
};

INSTANTIATE_TEST_CASE_P(Tests,
    HttpServerPropertiesManagerTest,
    ::testing::ValuesIn(kHttpServerPropertiesVersions));

TEST_P(HttpServerPropertiesManagerTest,
    SingleUpdateForTwoSpdyServerPrefChanges)
{
    ExpectCacheUpdate();

    // Set up the prefs for https://www.google.com and https://mail.google.com and
    // then set it twice. Only expect a single cache update.

    base::DictionaryValue* server_pref_dict = new base::DictionaryValue;
    url::SchemeHostPort google_server("https", "www.google.com", 443);
    url::SchemeHostPort mail_server("https", "mail.google.com", 443);

    // Set supports_spdy for https://www.google.com:443.
    server_pref_dict->SetBoolean("supports_spdy", true);

    // Set up alternative_services for https://www.google.com.
    std::unique_ptr<base::DictionaryValue> alternative_service_dict0(
        new base::DictionaryValue);
    alternative_service_dict0->SetInteger("port", 443);
    alternative_service_dict0->SetString("protocol_str", "h2");
    std::unique_ptr<base::DictionaryValue> alternative_service_dict1(
        new base::DictionaryValue);
    alternative_service_dict1->SetInteger("port", 1234);
    alternative_service_dict1->SetString("protocol_str", "quic");
    base::ListValue* alternative_service_list0 = new base::ListValue;
    alternative_service_list0->Append(std::move(alternative_service_dict0));
    alternative_service_list0->Append(std::move(alternative_service_dict1));
    server_pref_dict->SetWithoutPathExpansion("alternative_service",
        alternative_service_list0);

    // Set up ServerNetworkStats for https://www.google.com.
    base::DictionaryValue* stats = new base::DictionaryValue;
    stats->SetInteger("srtt", 10);
    server_pref_dict->SetWithoutPathExpansion("network_stats", stats);

    // Set the server preference for https://www.google.com.
    base::DictionaryValue* servers_dict = new base::DictionaryValue;
    servers_dict->SetWithoutPathExpansion(
        GetParam() >= 5 ? "https://www.google.com" : "www.google.com:443",
        server_pref_dict);
    base::ListValue* servers_list = nullptr;
    if (GetParam() >= 4) {
        servers_list = new base::ListValue;
        // |servers_list| takes ownership of |servers_dict|.
        servers_list->AppendIfNotPresent(servers_dict);
        servers_dict = new base::DictionaryValue;
    }

    // Set the preference for mail.google.com server.
    base::DictionaryValue* server_pref_dict1 = new base::DictionaryValue;

    // Set supports_spdy for https://mail.google.com.
    server_pref_dict1->SetBoolean("supports_spdy", true);

    // Set up alternative_services for https://mail.google.com.
    std::unique_ptr<base::DictionaryValue> alternative_service_dict2(
        new base::DictionaryValue);
    alternative_service_dict2->SetString("protocol_str", "npn-spdy/3.1");
    alternative_service_dict2->SetInteger("port", 444);
    base::ListValue* alternative_service_list1 = new base::ListValue;
    alternative_service_list1->Append(std::move(alternative_service_dict2));
    server_pref_dict1->SetWithoutPathExpansion("alternative_service",
        alternative_service_list1);

    // Set up ServerNetworkStats for https://mail.google.com and it is the MRU
    // server.
    base::DictionaryValue* stats1 = new base::DictionaryValue;
    stats1->SetInteger("srtt", 20);
    server_pref_dict1->SetWithoutPathExpansion("network_stats", stats1);
    // Set the server preference for https://mail.google.com.
    servers_dict->SetWithoutPathExpansion(
        GetParam() >= 5 ? "https://mail.google.com" : "mail.google.com:443",
        server_pref_dict1);
    base::DictionaryValue http_server_properties_dict;
    if (GetParam() >= 4) {
        // |servers_list| takes ownership of |servers_dict|.
        servers_list->AppendIfNotPresent(servers_dict);
        if (GetParam() == 5) {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict, -1);
        } else {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
                GetParam());
        }
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_list);
    } else {
        HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
            GetParam());
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_dict);
    }
    base::DictionaryValue* supports_quic = new base::DictionaryValue;
    supports_quic->SetBoolean("used_quic", true);
    supports_quic->SetString("address", "127.0.0.1");
    http_server_properties_dict.SetWithoutPathExpansion("supports_quic",
        supports_quic);

    // Set quic_server_info for https://www.google.com, https://mail.google.com
    // and https://play.google.com and verify the MRU.
    http_server_props_manager_->SetMaxServerConfigsStoredInProperties(3);
    base::DictionaryValue* quic_servers_dict = new base::DictionaryValue;
    base::DictionaryValue* quic_server_pref_dict1 = new base::DictionaryValue;
    std::string quic_server_info1("quic_server_info1");
    quic_server_pref_dict1->SetStringWithoutPathExpansion("server_info",
        quic_server_info1);
    base::DictionaryValue* quic_server_pref_dict2 = new base::DictionaryValue;
    std::string quic_server_info2("quic_server_info2");
    quic_server_pref_dict2->SetStringWithoutPathExpansion("server_info",
        quic_server_info2);
    base::DictionaryValue* quic_server_pref_dict3 = new base::DictionaryValue;
    std::string quic_server_info3("quic_server_info3");
    quic_server_pref_dict3->SetStringWithoutPathExpansion("server_info",
        quic_server_info3);
    // Set the quic_server_info1 for https://www.google.com.
    QuicServerId google_quic_server_id("www.google.com", 443);
    quic_servers_dict->SetWithoutPathExpansion(google_quic_server_id.ToString(),
        quic_server_pref_dict1);
    // Set the quic_server_info2 for https://mail.google.com.
    QuicServerId mail_quic_server_id("mail.google.com", 443);
    quic_servers_dict->SetWithoutPathExpansion(mail_quic_server_id.ToString(),
        quic_server_pref_dict2);
    // Set the quic_server_info3 for https://play.google.com.
    QuicServerId play_quic_server_id("play.google.com", 443);
    quic_servers_dict->SetWithoutPathExpansion(play_quic_server_id.ToString(),
        quic_server_pref_dict3);
    http_server_properties_dict.SetWithoutPathExpansion("quic_servers",
        quic_servers_dict);

    // Set the same value for kHttpServerProperties multiple times.
    pref_delegate_->SetPrefs(http_server_properties_dict);
    pref_delegate_->SetPrefs(http_server_properties_dict);

    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    // Verify SupportsSpdy.
    EXPECT_TRUE(
        http_server_props_manager_->SupportsRequestPriority(google_server));
    EXPECT_TRUE(http_server_props_manager_->SupportsRequestPriority(mail_server));
    HostPortPair foo_host_port_pair = HostPortPair::FromString("foo.google.com:1337");
    url::SchemeHostPort foo_server("http", foo_host_port_pair.host(),
        foo_host_port_pair.port());

    EXPECT_FALSE(http_server_props_manager_->SupportsRequestPriority(foo_server));

    // Verify alternative service.
    if (GetParam() >= 4) {
        const AlternativeServiceMap& map = http_server_props_manager_->alternative_service_map();
        ASSERT_EQ(2u, map.size());

        AlternativeServiceMap::const_iterator map_it = map.begin();
        EXPECT_EQ("mail.google.com", map_it->first.host());
        ASSERT_EQ(1u, map_it->second.size());
        EXPECT_EQ(NPN_SPDY_3_1, map_it->second[0].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[0].alternative_service.host.empty());
        EXPECT_EQ(444, map_it->second[0].alternative_service.port);
        ++map_it;
        EXPECT_EQ("www.google.com", map_it->first.host());
        ASSERT_EQ(2u, map_it->second.size());
        EXPECT_EQ(NPN_HTTP_2, map_it->second[0].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[0].alternative_service.host.empty());
        EXPECT_EQ(443, map_it->second[0].alternative_service.port);
        EXPECT_EQ(QUIC, map_it->second[1].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[1].alternative_service.host.empty());
        EXPECT_EQ(1234, map_it->second[1].alternative_service.port);
    } else {
        const AlternativeServiceMap& map = http_server_props_manager_->alternative_service_map();
        ASSERT_EQ(2u, map.size());
        AlternativeServiceMap::const_iterator map_it = map.begin();
        EXPECT_EQ("www.google.com", map_it->first.host());
        ASSERT_EQ(2u, map_it->second.size());
        EXPECT_EQ(NPN_HTTP_2, map_it->second[0].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[0].alternative_service.host.empty());
        EXPECT_EQ(443, map_it->second[0].alternative_service.port);
        EXPECT_EQ(QUIC, map_it->second[1].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[1].alternative_service.host.empty());
        EXPECT_EQ(1234, map_it->second[1].alternative_service.port);
        ++map_it;
        EXPECT_EQ("mail.google.com", map_it->first.host());
        ASSERT_EQ(1u, map_it->second.size());
        EXPECT_EQ(NPN_SPDY_3_1, map_it->second[0].alternative_service.protocol);
        EXPECT_TRUE(map_it->second[0].alternative_service.host.empty());
        EXPECT_EQ(444, map_it->second[0].alternative_service.port);
    }

    // Verify SupportsQuic.
    IPAddress last_address;
    EXPECT_TRUE(http_server_props_manager_->GetSupportsQuic(&last_address));
    EXPECT_EQ("127.0.0.1", last_address.ToString());

    /*
  // Verify ServerNetworkStats.
  const ServerNetworkStats* stats2 =
      http_server_props_manager_->GetServerNetworkStats(google_server);
  EXPECT_EQ(10, stats2->srtt.ToInternalValue());
  const ServerNetworkStats* stats3 =
      http_server_props_manager_->GetServerNetworkStats(mail_server);
  EXPECT_EQ(20, stats3->srtt.ToInternalValue());

  // Verify QuicServerInfo.
  EXPECT_EQ(quic_server_info1, *http_server_props_manager_->GetQuicServerInfo(
                                   google_quic_server_id));
  EXPECT_EQ(quic_server_info2, *http_server_props_manager_->GetQuicServerInfo(
                                   mail_quic_server_id));
  EXPECT_EQ(quic_server_info3, *http_server_props_manager_->GetQuicServerInfo(
                                   play_quic_server_id));

  // Verify the MRU order.
  http_server_props_manager_->SetMaxServerConfigsStoredInProperties(2);
  EXPECT_EQ(nullptr, http_server_props_manager_->GetQuicServerInfo(
                         google_quic_server_id));
  EXPECT_EQ(quic_server_info2, *http_server_props_manager_->GetQuicServerInfo(
                                   mail_quic_server_id));
  EXPECT_EQ(quic_server_info3, *http_server_props_manager_->GetQuicServerInfo(
                                   play_quic_server_id));
  */
}

TEST_P(HttpServerPropertiesManagerTest, BadCachedHostPortPair)
{
    ExpectCacheUpdate();
    // The prefs are automaticalls updated in the case corruption is detected.
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    base::DictionaryValue* server_pref_dict = new base::DictionaryValue;

    // Set supports_spdy for www.google.com:65536.
    server_pref_dict->SetBoolean("supports_spdy", true);

    // Set up alternative_service for www.google.com:65536.
    std::unique_ptr<base::DictionaryValue> alternative_service_dict(
        new base::DictionaryValue);
    alternative_service_dict->SetString("protocol_str", "npn-h2");
    alternative_service_dict->SetInteger("port", 80);
    base::ListValue* alternative_service_list = new base::ListValue;
    alternative_service_list->Append(std::move(alternative_service_dict));
    server_pref_dict->SetWithoutPathExpansion("alternative_service",
        alternative_service_list);

    // Set up ServerNetworkStats for www.google.com:65536.
    base::DictionaryValue* stats = new base::DictionaryValue;
    stats->SetInteger("srtt", 10);
    server_pref_dict->SetWithoutPathExpansion("network_stats", stats);

    // Set the server preference for www.google.com:65536.
    base::DictionaryValue* servers_dict = new base::DictionaryValue;
    servers_dict->SetWithoutPathExpansion("www.google.com:65536",
        server_pref_dict);
    base::DictionaryValue http_server_properties_dict;
    if (GetParam() >= 4) {
        base::ListValue* servers_list = new base::ListValue;
        // |servers_list| takes ownership of |servers_dict|.
        servers_list->AppendIfNotPresent(servers_dict);
        if (GetParam() == 5) {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict, -1);
        } else {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
                GetParam());
        }
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_list);
    } else {
        HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
            GetParam());
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_dict);
    }

    // Set quic_server_info for www.google.com:65536.
    base::DictionaryValue* quic_servers_dict = new base::DictionaryValue;
    base::DictionaryValue* quic_server_pref_dict1 = new base::DictionaryValue;
    quic_server_pref_dict1->SetStringWithoutPathExpansion("server_info",
        "quic_server_info1");
    quic_servers_dict->SetWithoutPathExpansion("http://mail.google.com:65536",
        quic_server_pref_dict1);

    http_server_properties_dict.SetWithoutPathExpansion("quic_servers",
        quic_servers_dict);

    // Set up the pref.
    pref_delegate_->SetPrefs(http_server_properties_dict);

    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    // Verify that nothing is set.
    HostPortPair google_host_port_pair = HostPortPair::FromString("www.google.com:65536");
    url::SchemeHostPort gooler_server("http", google_host_port_pair.host(),
        google_host_port_pair.port());

    EXPECT_FALSE(
        http_server_props_manager_->SupportsRequestPriority(gooler_server));
    EXPECT_FALSE(HasAlternativeService(gooler_server));
    const ServerNetworkStats* stats1 = http_server_props_manager_->GetServerNetworkStats(gooler_server);
    EXPECT_EQ(nullptr, stats1);
    EXPECT_EQ(0u, http_server_props_manager_->quic_server_info_map().size());
}

TEST_P(HttpServerPropertiesManagerTest, BadCachedAltProtocolPort)
{
    ExpectCacheUpdate();
    // The prefs are automaticalls updated in the case corruption is detected.
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    base::DictionaryValue* server_pref_dict = new base::DictionaryValue;

    // Set supports_spdy for www.google.com:80.
    server_pref_dict->SetBoolean("supports_spdy", true);

    // Set up alternative_service for www.google.com:80.
    std::unique_ptr<base::DictionaryValue> alternative_service_dict(
        new base::DictionaryValue);
    alternative_service_dict->SetString("protocol_str", "npn-h2");
    alternative_service_dict->SetInteger("port", 65536);
    base::ListValue* alternative_service_list = new base::ListValue;
    alternative_service_list->Append(std::move(alternative_service_dict));
    server_pref_dict->SetWithoutPathExpansion("alternative_service",
        alternative_service_list);

    // Set the server preference for www.google.com:80.
    base::DictionaryValue* servers_dict = new base::DictionaryValue;
    servers_dict->SetWithoutPathExpansion("www.google.com:80", server_pref_dict);
    base::DictionaryValue http_server_properties_dict;
    if (GetParam() >= 4) {
        base::ListValue* servers_list = new base::ListValue;
        // |servers_list| takes ownership of |servers_dict|.
        servers_list->AppendIfNotPresent(servers_dict);
        if (GetParam() == 5) {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict, -1);
        } else {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
                GetParam());
        }
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_list);
    } else {
        HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
            GetParam());
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_dict);
    }

    // Set up the pref.
    pref_delegate_->SetPrefs(http_server_properties_dict);

    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    // Verify alternative service is not set.
    EXPECT_FALSE(
        HasAlternativeService(url::SchemeHostPort("http", "www.google.com", 80)));
}

TEST_P(HttpServerPropertiesManagerTest, SupportsSpdy)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    // Post an update task to the network thread. SetSupportsSpdy calls
    // ScheduleUpdatePrefsOnNetworkThread.

    // Add mail.google.com:443 as a supporting spdy server.
    url::SchemeHostPort spdy_server("https", "mail.google.com", 443);
    EXPECT_FALSE(
        http_server_props_manager_->SupportsRequestPriority(spdy_server));
    http_server_props_manager_->SetSupportsSpdy(spdy_server, true);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetSupportsSpdy(spdy_server, true);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    EXPECT_TRUE(http_server_props_manager_->SupportsRequestPriority(spdy_server));
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
}

TEST_P(HttpServerPropertiesManagerTest, SetSpdySetting)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    // Add SpdySetting for mail.google.com:443.
    url::SchemeHostPort spdy_server_mail("https", "mail.google.com", 443);
    const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
    const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
    const uint32_t value1 = 31337;
    http_server_props_manager_->SetSpdySetting(
        spdy_server_mail, id1, flags1, value1);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    const SettingsMap& settings_map1_ret = http_server_props_manager_->GetSpdySettings(spdy_server_mail);
    ASSERT_EQ(1U, settings_map1_ret.size());
    SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
    EXPECT_TRUE(it1_ret != settings_map1_ret.end());
    SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
    EXPECT_EQ(value1, flags_and_value1_ret.second);

    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
}

TEST_P(HttpServerPropertiesManagerTest, ClearSpdySetting)
{
    ExpectPrefsUpdateRepeatedly();
    ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly();

    // Add SpdySetting for mail.google.com:443.
    url::SchemeHostPort spdy_server_mail("https", "mail.google.com", 443);
    const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
    const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
    const uint32_t value1 = 31337;
    http_server_props_manager_->SetSpdySetting(
        spdy_server_mail, id1, flags1, value1);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    const SettingsMap& settings_map1_ret = http_server_props_manager_->GetSpdySettings(spdy_server_mail);
    ASSERT_EQ(1U, settings_map1_ret.size());
    SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
    EXPECT_TRUE(it1_ret != settings_map1_ret.end());
    SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
    EXPECT_EQ(value1, flags_and_value1_ret.second);

    // Clear SpdySetting for mail.google.com:443.
    http_server_props_manager_->ClearSpdySettings(spdy_server_mail);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    // Verify that there are no entries in the settings map for
    // mail.google.com:443.
    const SettingsMap& settings_map2_ret = http_server_props_manager_->GetSpdySettings(spdy_server_mail);
    ASSERT_EQ(0U, settings_map2_ret.size());

    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
}

TEST_P(HttpServerPropertiesManagerTest, ClearAllSpdySetting)
{
    ExpectPrefsUpdateRepeatedly();
    ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly();

    // Add SpdySetting for mail.google.com:443.
    url::SchemeHostPort spdy_server_mail("https", "mail.google.com", 443);
    const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
    const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
    const uint32_t value1 = 31337;
    http_server_props_manager_->SetSpdySetting(
        spdy_server_mail, id1, flags1, value1);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    const SettingsMap& settings_map1_ret = http_server_props_manager_->GetSpdySettings(spdy_server_mail);
    ASSERT_EQ(1U, settings_map1_ret.size());
    SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
    EXPECT_TRUE(it1_ret != settings_map1_ret.end());
    SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
    EXPECT_EQ(value1, flags_and_value1_ret.second);

    // Clear All SpdySettings.
    http_server_props_manager_->ClearAllSpdySettings();

    // Run the task.
    base::RunLoop().RunUntilIdle();

    // Verify that there are no entries in the settings map.
    const SpdySettingsMap& spdy_settings_map2_ret = http_server_props_manager_->spdy_settings_map();
    ASSERT_EQ(0U, spdy_settings_map2_ret.size());

    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
}

TEST_P(HttpServerPropertiesManagerTest, GetAlternativeServices)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    url::SchemeHostPort spdy_server_mail("http", "mail.google.com", 80);
    EXPECT_FALSE(HasAlternativeService(spdy_server_mail));
    const AlternativeService alternative_service(NPN_HTTP_2, "mail.google.com",
        443);
    http_server_props_manager_->SetAlternativeService(
        spdy_server_mail, alternative_service, one_day_from_now_);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetAlternativeService(
        spdy_server_mail, alternative_service, one_day_from_now_);

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    AlternativeServiceVector alternative_service_vector = http_server_props_manager_->GetAlternativeServices(spdy_server_mail);
    ASSERT_EQ(1u, alternative_service_vector.size());
    EXPECT_EQ(alternative_service, alternative_service_vector[0]);
}

TEST_P(HttpServerPropertiesManagerTest, SetAlternativeServices)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    url::SchemeHostPort spdy_server_mail("http", "mail.google.com", 80);
    EXPECT_FALSE(HasAlternativeService(spdy_server_mail));
    AlternativeServiceInfoVector alternative_service_info_vector;
    const AlternativeService alternative_service1(NPN_HTTP_2, "mail.google.com",
        443);
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(alternative_service1, one_day_from_now_));
    const AlternativeService alternative_service2(QUIC, "mail.google.com", 1234);
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(alternative_service2, one_day_from_now_));
    http_server_props_manager_->SetAlternativeServices(
        spdy_server_mail, alternative_service_info_vector);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetAlternativeServices(
        spdy_server_mail, alternative_service_info_vector);

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    AlternativeServiceVector alternative_service_vector = http_server_props_manager_->GetAlternativeServices(spdy_server_mail);
    ASSERT_EQ(2u, alternative_service_vector.size());
    EXPECT_EQ(alternative_service1, alternative_service_vector[0]);
    EXPECT_EQ(alternative_service2, alternative_service_vector[1]);
}

TEST_P(HttpServerPropertiesManagerTest, SetAlternativeServicesEmpty)
{
    url::SchemeHostPort spdy_server_mail("http", "mail.google.com", 80);
    EXPECT_FALSE(HasAlternativeService(spdy_server_mail));
    const AlternativeService alternative_service(NPN_HTTP_2, "mail.google.com",
        443);
    http_server_props_manager_->SetAlternativeServices(
        spdy_server_mail, AlternativeServiceInfoVector());
    // ExpectScheduleUpdatePrefsOnNetworkThread() should not be called.

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    EXPECT_FALSE(HasAlternativeService(spdy_server_mail));
}

TEST_P(HttpServerPropertiesManagerTest, ConfirmAlternativeService)
{
    ExpectPrefsUpdate();

    url::SchemeHostPort spdy_server_mail("http", "mail.google.com", 80);
    EXPECT_FALSE(HasAlternativeService(spdy_server_mail));
    AlternativeService alternative_service(NPN_HTTP_2, "mail.google.com", 443);

    ExpectScheduleUpdatePrefsOnNetworkThread();
    http_server_props_manager_->SetAlternativeService(
        spdy_server_mail, alternative_service, one_day_from_now_);

    EXPECT_FALSE(http_server_props_manager_->IsAlternativeServiceBroken(
        alternative_service));
    EXPECT_FALSE(http_server_props_manager_->WasAlternativeServiceRecentlyBroken(
        alternative_service));

    ExpectScheduleUpdatePrefsOnNetworkThread();
    http_server_props_manager_->MarkAlternativeServiceBroken(alternative_service);
    EXPECT_TRUE(http_server_props_manager_->IsAlternativeServiceBroken(
        alternative_service));
    EXPECT_TRUE(http_server_props_manager_->WasAlternativeServiceRecentlyBroken(
        alternative_service));

    ExpectScheduleUpdatePrefsOnNetworkThread();
    http_server_props_manager_->ConfirmAlternativeService(alternative_service);
    EXPECT_FALSE(http_server_props_manager_->IsAlternativeServiceBroken(
        alternative_service));
    EXPECT_FALSE(http_server_props_manager_->WasAlternativeServiceRecentlyBroken(
        alternative_service));
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->ConfirmAlternativeService(alternative_service);
    EXPECT_FALSE(http_server_props_manager_->IsAlternativeServiceBroken(
        alternative_service));
    EXPECT_FALSE(http_server_props_manager_->WasAlternativeServiceRecentlyBroken(
        alternative_service));

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    EXPECT_FALSE(http_server_props_manager_->IsAlternativeServiceBroken(
        alternative_service));
    EXPECT_FALSE(http_server_props_manager_->WasAlternativeServiceRecentlyBroken(
        alternative_service));
}

TEST_P(HttpServerPropertiesManagerTest, SupportsQuic)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    IPAddress address;
    EXPECT_FALSE(http_server_props_manager_->GetSupportsQuic(&address));

    IPAddress actual_address(127, 0, 0, 1);
    http_server_props_manager_->SetSupportsQuic(true, actual_address);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetSupportsQuic(true, actual_address);

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    EXPECT_TRUE(http_server_props_manager_->GetSupportsQuic(&address));
    EXPECT_EQ(actual_address, address);
}

TEST_P(HttpServerPropertiesManagerTest, ServerNetworkStats)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    url::SchemeHostPort mail_server("http", "mail.google.com", 80);
    const ServerNetworkStats* stats = http_server_props_manager_->GetServerNetworkStats(mail_server);
    EXPECT_EQ(nullptr, stats);
    ServerNetworkStats stats1;
    stats1.srtt = base::TimeDelta::FromMicroseconds(10);
    http_server_props_manager_->SetServerNetworkStats(mail_server, stats1);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetServerNetworkStats(mail_server, stats1);

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    const ServerNetworkStats* stats2 = http_server_props_manager_->GetServerNetworkStats(mail_server);
    EXPECT_EQ(10, stats2->srtt.ToInternalValue());
}

TEST_P(HttpServerPropertiesManagerTest, QuicServerInfo)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThread();

    QuicServerId mail_quic_server_id("mail.google.com", 80);
    EXPECT_EQ(nullptr,
        http_server_props_manager_->GetQuicServerInfo(mail_quic_server_id));
    std::string quic_server_info1("quic_server_info1");
    http_server_props_manager_->SetQuicServerInfo(mail_quic_server_id,
        quic_server_info1);
    // ExpectScheduleUpdatePrefsOnNetworkThread() should be called only once.
    http_server_props_manager_->SetQuicServerInfo(mail_quic_server_id,
        quic_server_info1);

    // Run the task.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    EXPECT_EQ(quic_server_info1, *http_server_props_manager_->GetQuicServerInfo(mail_quic_server_id));
}

TEST_P(HttpServerPropertiesManagerTest, Clear)
{
    ExpectPrefsUpdate();
    ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly();

    url::SchemeHostPort spdy_server("https", "mail.google.com", 443);
    http_server_props_manager_->SetSupportsSpdy(spdy_server, true);
    AlternativeService alternative_service(NPN_HTTP_2, "mail.google.com", 1234);
    http_server_props_manager_->SetAlternativeService(
        spdy_server, alternative_service, one_day_from_now_);
    IPAddress actual_address(127, 0, 0, 1);
    http_server_props_manager_->SetSupportsQuic(true, actual_address);
    ServerNetworkStats stats;
    stats.srtt = base::TimeDelta::FromMicroseconds(10);
    http_server_props_manager_->SetServerNetworkStats(spdy_server, stats);

    QuicServerId mail_quic_server_id("mail.google.com", 80);
    std::string quic_server_info1("quic_server_info1");
    http_server_props_manager_->SetQuicServerInfo(mail_quic_server_id,
        quic_server_info1);

    const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
    const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
    const uint32_t value1 = 31337;
    http_server_props_manager_->SetSpdySetting(spdy_server, id1, flags1, value1);

    // Run the task.
    base::RunLoop().RunUntilIdle();

    EXPECT_TRUE(http_server_props_manager_->SupportsRequestPriority(spdy_server));
    EXPECT_TRUE(HasAlternativeService(spdy_server));
    IPAddress address;
    EXPECT_TRUE(http_server_props_manager_->GetSupportsQuic(&address));
    EXPECT_EQ(actual_address, address);
    const ServerNetworkStats* stats1 = http_server_props_manager_->GetServerNetworkStats(spdy_server);
    EXPECT_EQ(10, stats1->srtt.ToInternalValue());
    EXPECT_EQ(quic_server_info1, *http_server_props_manager_->GetQuicServerInfo(mail_quic_server_id));

    // Check SPDY settings values.
    const SettingsMap& settings_map1_ret = http_server_props_manager_->GetSpdySettings(spdy_server);
    ASSERT_EQ(1U, settings_map1_ret.size());
    SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
    EXPECT_TRUE(it1_ret != settings_map1_ret.end());
    SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
    EXPECT_EQ(value1, flags_and_value1_ret.second);

    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    ExpectPrefsUpdate();

    // Clear http server data, time out if we do not get a completion callback.
    http_server_props_manager_->Clear(base::MessageLoop::QuitWhenIdleClosure());
    base::RunLoop().Run();

    EXPECT_FALSE(
        http_server_props_manager_->SupportsRequestPriority(spdy_server));
    EXPECT_FALSE(HasAlternativeService(spdy_server));
    EXPECT_FALSE(http_server_props_manager_->GetSupportsQuic(&address));
    const ServerNetworkStats* stats2 = http_server_props_manager_->GetServerNetworkStats(spdy_server);
    EXPECT_EQ(nullptr, stats2);
    EXPECT_EQ(nullptr,
        http_server_props_manager_->GetQuicServerInfo(mail_quic_server_id));

    const SettingsMap& settings_map2_ret = http_server_props_manager_->GetSpdySettings(spdy_server);
    EXPECT_EQ(0U, settings_map2_ret.size());

    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
}

// https://crbug.com/444956: Add 200 alternative_service servers followed by
// supports_quic and verify we have read supports_quic from prefs.
TEST_P(HttpServerPropertiesManagerTest, BadSupportsQuic)
{
    ExpectCacheUpdate();

    base::DictionaryValue* servers_dict = new base::DictionaryValue;
    base::ListValue* servers_list = nullptr;
    if (GetParam() >= 4)
        servers_list = new base::ListValue;

    for (int i = 1; i <= 200; ++i) {
        // Set up alternative_service for www.google.com:i.
        std::unique_ptr<base::DictionaryValue> alternative_service_dict(
            new base::DictionaryValue);
        alternative_service_dict->SetString("protocol_str", "quic");
        alternative_service_dict->SetInteger("port", i);
        base::ListValue* alternative_service_list = new base::ListValue;
        alternative_service_list->Append(std::move(alternative_service_dict));
        base::DictionaryValue* server_pref_dict = new base::DictionaryValue;
        server_pref_dict->SetWithoutPathExpansion("alternative_service",
            alternative_service_list);
        if (GetParam() >= 5) {
            servers_dict->SetWithoutPathExpansion(
                StringPrintf("https://www.google.com:%d", i), server_pref_dict);
        } else {
            servers_dict->SetWithoutPathExpansion(
                StringPrintf("www.google.com:%d", i), server_pref_dict);
        }
        if (GetParam() >= 4) {
            // |servers_list| takes ownership of |servers_dict|.
            servers_list->AppendIfNotPresent(servers_dict);
            servers_dict = new base::DictionaryValue;
        }
    }

    // Set the server preference for http://mail.google.com server.
    base::DictionaryValue* server_pref_dict1 = new base::DictionaryValue;
    if (GetParam() >= 5) {
        servers_dict->SetWithoutPathExpansion("https://mail.google.com",
            server_pref_dict1);
    } else {
        servers_dict->SetWithoutPathExpansion("mail.google.com:80",
            server_pref_dict1);
    }
    base::DictionaryValue http_server_properties_dict;
    if (GetParam() >= 4) {
        // |servers_list| takes ownership of |servers_dict|.
        servers_list->AppendIfNotPresent(servers_dict);
        if (GetParam() == 5) {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict, -1);
        } else {
            HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
                GetParam());
        }
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_list);
    } else {
        HttpServerPropertiesManager::SetVersion(&http_server_properties_dict,
            GetParam());
        http_server_properties_dict.SetWithoutPathExpansion("servers",
            servers_dict);
    }

    // Set up SupportsQuic for 127.0.0.1
    base::DictionaryValue* supports_quic = new base::DictionaryValue;
    supports_quic->SetBoolean("used_quic", true);
    supports_quic->SetString("address", "127.0.0.1");
    http_server_properties_dict.SetWithoutPathExpansion("supports_quic",
        supports_quic);

    // Set up the pref.
    pref_delegate_->SetPrefs(http_server_properties_dict);

    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());

    // Verify alternative service.
    for (int i = 1; i <= 200; ++i) {
        GURL server_gurl;
        if (GetParam() >= 5) {
            server_gurl = GURL(StringPrintf("https://www.google.com:%d", i));
        } else {
            server_gurl = GURL(StringPrintf("https://www.google.com:%d", i));
        }
        url::SchemeHostPort server(server_gurl);
        AlternativeServiceVector alternative_service_vector = http_server_props_manager_->GetAlternativeServices(server);
        ASSERT_EQ(1u, alternative_service_vector.size());
        EXPECT_EQ(QUIC, alternative_service_vector[0].protocol);
        EXPECT_EQ(i, alternative_service_vector[0].port);
    }

    // Verify SupportsQuic.
    IPAddress address;
    ASSERT_TRUE(http_server_props_manager_->GetSupportsQuic(&address));
    EXPECT_EQ("127.0.0.1", address.ToString());
}

TEST_P(HttpServerPropertiesManagerTest, UpdateCacheWithPrefs)
{
    ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly();

    const url::SchemeHostPort server_www("http", "www.google.com", 80);
    const url::SchemeHostPort server_mail("http", "mail.google.com", 80);

    // Set alternate protocol.
    AlternativeServiceInfoVector alternative_service_info_vector;
    AlternativeService www_alternative_service1(NPN_HTTP_2, "", 443);
    base::Time expiration1;
    ASSERT_TRUE(base::Time::FromUTCString("2036-12-01 10:00:00", &expiration1));
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(www_alternative_service1, expiration1));
    AlternativeService www_alternative_service2(NPN_HTTP_2, "www.google.com",
        1234);
    base::Time expiration2;
    ASSERT_TRUE(base::Time::FromUTCString("2036-12-31 10:00:00", &expiration2));
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(www_alternative_service2, expiration2));
    http_server_props_manager_->SetAlternativeServices(
        server_www, alternative_service_info_vector);

    AlternativeService mail_alternative_service(NPN_SPDY_3_1, "foo.google.com",
        444);
    base::Time expiration3 = base::Time::Max();
    http_server_props_manager_->SetAlternativeService(
        server_mail, mail_alternative_service, expiration3);

    // Set ServerNetworkStats.
    ServerNetworkStats stats;
    stats.srtt = base::TimeDelta::FromInternalValue(42);
    http_server_props_manager_->SetServerNetworkStats(server_mail, stats);

    // Set quic_server_info string.
    QuicServerId mail_quic_server_id("mail.google.com", 80);
    std::string quic_server_info1("quic_server_info1");
    http_server_props_manager_->SetQuicServerInfo(mail_quic_server_id,
        quic_server_info1);

    // Set SupportsQuic.
    IPAddress actual_address(127, 0, 0, 1);
    http_server_props_manager_->SetSupportsQuic(true, actual_address);

    // Update cache.
    ExpectPrefsUpdate();
    ExpectCacheUpdate();
    http_server_props_manager_->ScheduleUpdateCacheOnPrefThread();
    base::RunLoop().RunUntilIdle();

    // Verify preferences.
    const char expected_json[] = "{\"quic_servers\":{\"https://"
                                 "mail.google.com:80\":{\"server_info\":\"quic_server_info1\"}},"
                                 "\"servers\":["
                                 "{\"http://www.google.com\":{"
                                 "\"alternative_service\":[{\"expiration\":\"13756212000000000\","
                                 "\"port\":443,\"protocol_str\":\"h2\"},"
                                 "{\"expiration\":\"13758804000000000\",\"host\":\"www.google.com\","
                                 "\"port\":1234,\"protocol_str\":\"h2\"}]}},"
                                 "{\"http://mail.google.com\":{\"alternative_service\":[{"
                                 "\"expiration\":\"9223372036854775807\",\"host\":\"foo.google.com\","
                                 "\"port\":444,\"protocol_str\":\"npn-spdy/3.1\"}],"
                                 "\"network_stats\":{\"srtt\":42}}}"
                                 "],"
                                 "\"supports_quic\":{\"address\":\"127.0.0.1\",\"used_quic\":true},"
                                 "\"version\":5}";

    const base::Value* http_server_properties = &pref_delegate_->GetServerProperties();
    std::string preferences_json;
    EXPECT_TRUE(
        base::JSONWriter::Write(*http_server_properties, &preferences_json));
    EXPECT_EQ(expected_json, preferences_json);
}

TEST_P(HttpServerPropertiesManagerTest, AddToAlternativeServiceMap)
{
    std::unique_ptr<base::Value> server_value = base::JSONReader::Read(
        "{\"alternative_service\":[{\"port\":443,\"protocol_str\":\"h2\"},"
        "{\"port\":123,\"protocol_str\":\"quic\","
        "\"expiration\":\"9223372036854775807\"},{\"host\":\"example.org\","
        "\"port\":1234,\"protocol_str\":\"npn-h2\","
        "\"expiration\":\"13758804000000000\"}]}");
    ASSERT_TRUE(server_value);
    base::DictionaryValue* server_dict;
    ASSERT_TRUE(server_value->GetAsDictionary(&server_dict));

    const url::SchemeHostPort server("https", "example.com", 443);
    AlternativeServiceMap alternative_service_map(/*max_size=*/5);
    EXPECT_TRUE(http_server_props_manager_->AddToAlternativeServiceMap(
        server, *server_dict, &alternative_service_map));

    AlternativeServiceMap::iterator it = alternative_service_map.Get(server);
    ASSERT_NE(alternative_service_map.end(), it);
    AlternativeServiceInfoVector alternative_service_info_vector = it->second;
    ASSERT_EQ(3u, alternative_service_info_vector.size());

    EXPECT_EQ(NPN_HTTP_2,
        alternative_service_info_vector[0].alternative_service.protocol);
    EXPECT_EQ("", alternative_service_info_vector[0].alternative_service.host);
    EXPECT_EQ(443, alternative_service_info_vector[0].alternative_service.port);
    // Expiration defaults to one day from now, testing with tolerance.
    const base::Time now = base::Time::Now();
    const base::Time expiration = alternative_service_info_vector[0].expiration;
    EXPECT_LE(now + base::TimeDelta::FromHours(23), expiration);
    EXPECT_GE(now + base::TimeDelta::FromDays(1), expiration);

    EXPECT_EQ(QUIC,
        alternative_service_info_vector[1].alternative_service.protocol);
    EXPECT_EQ("", alternative_service_info_vector[1].alternative_service.host);
    EXPECT_EQ(123, alternative_service_info_vector[1].alternative_service.port);
    // numeric_limits<int64_t>::max() represents base::Time::Max().
    EXPECT_EQ(base::Time::Max(), alternative_service_info_vector[1].expiration);

    EXPECT_EQ(NPN_HTTP_2,
        alternative_service_info_vector[2].alternative_service.protocol);
    EXPECT_EQ("example.org",
        alternative_service_info_vector[2].alternative_service.host);
    EXPECT_EQ(1234, alternative_service_info_vector[2].alternative_service.port);
    base::Time expected_expiration;
    ASSERT_TRUE(
        base::Time::FromUTCString("2036-12-31 10:00:00", &expected_expiration));
    EXPECT_EQ(expected_expiration, alternative_service_info_vector[2].expiration);
}

// Regression test for https://crbug.com/615497.
TEST_P(HttpServerPropertiesManagerTest, DoNotLoadAltSvcForInsecureOrigins)
{
    std::unique_ptr<base::Value> server_value = base::JSONReader::Read(
        "{\"alternative_service\":[{\"port\":443,\"protocol_str\":\"npn-h2\","
        "\"expiration\":\"9223372036854775807\"}]}");
    ASSERT_TRUE(server_value);
    base::DictionaryValue* server_dict;
    ASSERT_TRUE(server_value->GetAsDictionary(&server_dict));

    const url::SchemeHostPort server("http", "example.com", 80);
    AlternativeServiceMap alternative_service_map(/*max_size=*/5);
    EXPECT_FALSE(http_server_props_manager_->AddToAlternativeServiceMap(
        server, *server_dict, &alternative_service_map));

    AlternativeServiceMap::iterator it = alternative_service_map.Get(server);
    EXPECT_EQ(alternative_service_map.end(), it);
}

// Do not persist expired or broken alternative service entries to disk.
TEST_P(HttpServerPropertiesManagerTest,
    DoNotPersistExpiredOrBrokenAlternativeService)
{
    ExpectScheduleUpdatePrefsOnNetworkThreadRepeatedly();

    AlternativeServiceInfoVector alternative_service_info_vector;

    const AlternativeService broken_alternative_service(
        NPN_HTTP_2, "broken.example.com", 443);
    const base::Time time_one_day_later = base::Time::Now() + base::TimeDelta::FromDays(1);
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(broken_alternative_service, time_one_day_later));
    http_server_props_manager_->MarkAlternativeServiceBroken(
        broken_alternative_service);

    const AlternativeService expired_alternative_service(
        NPN_HTTP_2, "expired.example.com", 443);
    const base::Time time_one_day_ago = base::Time::Now() - base::TimeDelta::FromDays(1);
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(expired_alternative_service, time_one_day_ago));

    const AlternativeService valid_alternative_service(NPN_HTTP_2,
        "valid.example.com", 443);
    alternative_service_info_vector.push_back(
        AlternativeServiceInfo(valid_alternative_service, time_one_day_later));

    const url::SchemeHostPort server("https", "www.example.com", 443);
    http_server_props_manager_->SetAlternativeServices(
        server, alternative_service_info_vector);

    // Update cache.
    ExpectPrefsUpdate();
    ExpectCacheUpdate();
    http_server_props_manager_->ScheduleUpdateCacheOnPrefThread();
    base::RunLoop().RunUntilIdle();

    const base::DictionaryValue& pref_dict = pref_delegate_->GetServerProperties();

    const base::ListValue* servers_list = nullptr;
    ASSERT_TRUE(pref_dict.GetListWithoutPathExpansion("servers", &servers_list));
    base::ListValue::const_iterator it = servers_list->begin();
    const base::DictionaryValue* server_pref_dict;
    ASSERT_TRUE((*it)->GetAsDictionary(&server_pref_dict));

    const base::DictionaryValue* example_pref_dict;

    ASSERT_TRUE(server_pref_dict->GetDictionaryWithoutPathExpansion(
        "https://www.example.com", &example_pref_dict));

    const base::ListValue* altsvc_list;
    ASSERT_TRUE(example_pref_dict->GetList("alternative_service", &altsvc_list));

    ASSERT_EQ(1u, altsvc_list->GetSize());

    const base::DictionaryValue* altsvc_entry;
    ASSERT_TRUE(altsvc_list->GetDictionary(0, &altsvc_entry));

    std::string hostname;
    ASSERT_TRUE(altsvc_entry->GetString("host", &hostname));
    EXPECT_EQ("valid.example.com", hostname);
}

// Test that expired alternative service entries on disk are ignored.
TEST_P(HttpServerPropertiesManagerTest, DoNotLoadExpiredAlternativeService)
{
    std::unique_ptr<base::ListValue> alternative_service_list(
        new base::ListValue);
    std::unique_ptr<base::DictionaryValue> expired_dict(
        new base::DictionaryValue);
    expired_dict->SetString("protocol_str", "npn-h2");
    expired_dict->SetString("host", "expired.example.com");
    expired_dict->SetInteger("port", 443);
    base::Time time_one_day_ago = base::Time::Now() - base::TimeDelta::FromDays(1);
    expired_dict->SetString(
        "expiration", base::Int64ToString(time_one_day_ago.ToInternalValue()));
    alternative_service_list->Append(std::move(expired_dict));

    std::unique_ptr<base::DictionaryValue> valid_dict(new base::DictionaryValue);
    valid_dict->SetString("protocol_str", "npn-h2");
    valid_dict->SetString("host", "valid.example.com");
    valid_dict->SetInteger("port", 443);
    valid_dict->SetString(
        "expiration", base::Int64ToString(one_day_from_now_.ToInternalValue()));
    alternative_service_list->Append(std::move(valid_dict));

    base::DictionaryValue server_pref_dict;
    server_pref_dict.SetWithoutPathExpansion("alternative_service",
        alternative_service_list.release());

    const url::SchemeHostPort server("https", "example.com", 443);
    AlternativeServiceMap alternative_service_map(/*max_size=*/5);
    ASSERT_TRUE(http_server_props_manager_->AddToAlternativeServiceMap(
        server, server_pref_dict, &alternative_service_map));

    AlternativeServiceMap::iterator it = alternative_service_map.Get(server);
    ASSERT_NE(alternative_service_map.end(), it);
    AlternativeServiceInfoVector alternative_service_info_vector = it->second;
    ASSERT_EQ(1u, alternative_service_info_vector.size());

    EXPECT_EQ(NPN_HTTP_2,
        alternative_service_info_vector[0].alternative_service.protocol);
    EXPECT_EQ("valid.example.com",
        alternative_service_info_vector[0].alternative_service.host);
    EXPECT_EQ(443, alternative_service_info_vector[0].alternative_service.port);
    EXPECT_EQ(one_day_from_now_, alternative_service_info_vector[0].expiration);
}

TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdateCache0)
{
    // Post an update task to the UI thread.
    http_server_props_manager_->ScheduleUpdateCacheOnPrefThread();
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    http_server_props_manager_.reset();
    // Run the task after shutdown and deletion.
    base::RunLoop().RunUntilIdle();
}

TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdateCache1)
{
    // Post an update task.
    http_server_props_manager_->ScheduleUpdateCacheOnPrefThread();
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    // Run the task after shutdown, but before deletion.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
    http_server_props_manager_.reset();
    base::RunLoop().RunUntilIdle();
}

TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdateCache2)
{
    http_server_props_manager_->UpdateCacheFromPrefsOnUIConcrete();
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    // Run the task after shutdown, but before deletion.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
    http_server_props_manager_.reset();
    base::RunLoop().RunUntilIdle();
}

//
// Tests for shutdown when updating prefs.
//
TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdatePrefs0)
{
    // Post an update task to the IO thread.
    http_server_props_manager_->ScheduleUpdatePrefsOnNetworkThread();
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    http_server_props_manager_.reset();
    // Run the task after shutdown and deletion.
    base::RunLoop().RunUntilIdle();
}

TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdatePrefs1)
{
    ExpectPrefsUpdate();
    // Post an update task.
    http_server_props_manager_->ScheduleUpdatePrefsOnNetworkThread();
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    // Run the task after shutdown, but before deletion.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
    http_server_props_manager_.reset();
    base::RunLoop().RunUntilIdle();
}

TEST_P(HttpServerPropertiesManagerTest, ShutdownWithPendingUpdatePrefs2)
{
    // This posts a task to the UI thread.
    http_server_props_manager_->UpdatePrefsFromCacheOnNetworkThreadConcrete(
        base::Closure());
    // Shutdown comes before the task is executed.
    http_server_props_manager_->ShutdownOnPrefThread();
    // Run the task after shutdown, but before deletion.
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(http_server_props_manager_.get());
    http_server_props_manager_.reset();
    base::RunLoop().RunUntilIdle();
}

} // namespace net
